#include "Application.h"
#include "SFML/Graphics.hpp"

#include <cassert>

using namespace phynet;

Application::Application(bool fullscreen)
: m_eventObserver(0),
  m_fontSize(16),
  m_is3D(false),
  m_red(0.f),
  m_green(0.f),
  m_blue(0.f),
  m_alpha(1.f)
{
	m_window = new sf::RenderWindow(sf::VideoMode(1024, 768, 32),
								"phy sandbox",
								fullscreen ? sf::Style::Fullscreen : sf::Style::Close);

	m_window->PreserveOpenGLStates(true);
	m_window->SetFramerateLimit(0);

	m_font = new sf::Font;
	bool success = m_font->LoadFromFile("consola.ttf", m_fontSize);
	assert(success);success;

	Init();
}

Application::~Application()
{
	if (m_font)
		delete m_font;

	if (m_window)
		delete m_window;
}

void Application::Init()
{
	Prepare3DViewport(0, 0, m_window->GetWidth(), m_window->GetHeight());

	/* viewer position */
	glMatrixMode(GL_MODELVIEW);
	glTranslatef(0.0f, -10.0f, -40.0f);

	// render is called once in order to init the client window
	Render();
}

void Application::Run()
{
	sf::Event event;

	while (m_window && m_window->IsOpened())
	{
		// system events handling first
		while (m_window->GetEvent(event))
		{
			switch (event.Type)
			{
			case sf::Event::KeyPressed:
				if (m_eventObserver)
					m_eventObserver->OnKeyDown(event.Key.Code);
				break;

			case sf::Event::KeyReleased:
				if (event.Key.Code == sf::Key::Escape)
				{
					m_window->Close();
				}
				else if (m_eventObserver)
				{
					m_eventObserver->OnKeyUp(event.Key.Code);
				}
				break;

			case sf::Event::Closed:
				m_window->Close();
				break;

			case sf::Event::Resized:
				OnResize(event.Size.Width, event.Size.Height);
				break;

			}
		}

		m_window->SetActive(true);

		// there's some spare time to run the application itself
		if (m_eventObserver)
			m_eventObserver->OnIdle();
	}
}

void Application::Init3D()
{
	glClearColor(m_red, m_green, m_blue, m_alpha);
	glClearDepth(1.0f);						// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);				// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);					// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	m_is3D = true;
}

void Application::Init2D()
{
	glClearColor(m_red, m_green, m_blue, m_alpha);
	glEnable(GL_TEXTURE_2D);				// textures
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	m_is3D = false;
}

void Application::SetObserver(ApplicationEventObserver* observer)
{
	m_eventObserver = observer;
}

void Application::SetBackgroundColour(float red, float green, float blue, float alpha)
{
	m_red = red;
	m_green = green;
	m_blue = blue;
	m_alpha = alpha;
}

void Application::Render()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	m_nextTextLinePosition.Zero();

	if (m_eventObserver)
 		m_eventObserver->OnPaint();

	glFlush();
	m_window->Display();
}

void Application::RenderLine(const Vector2& start, const Vector2& end)
{
	glBegin(GL_LINE_LOOP);
	glVertex2f(start.X(), start.Y());
	glVertex2f(end.X(), end.Y());
	glEnd();
}

// approximates a circle by drawing numSegments segments
void Application::RenderCircle(const Vector2 &position, Real radius, UInt numSegments)
{
	using namespace phynet::math;

	Real angularStep = 2 * 3.1415926f / Real(numSegments); 

	Real cos = cosf(angularStep);
	Real sin = sinf(angularStep);

	Real oldX;

	Real x = radius; //we start at angle = 0 
	Real y = 0.f; 

	glBegin(GL_LINE_LOOP); 
	int i = numSegments;
	while (i--) 
	{ 
		glVertex2f(x + position.X(), y + position.Y());

		//apply the rotation matrix
		oldX = x;
		x = cos * x - sin * y;
		y = sin * oldX + cos * y;
	} 
	glEnd(); 
}


void Application::RenderBox(const Vector2& p1, const Vector2& p2, const Vector2& p3, const Vector2& p4)
{
	glBegin(GL_LINE_LOOP);
	glVertex2f(p1.X(), p1.Y());
	glVertex2f(p2.X(), p2.Y());
	glVertex2f(p3.X(), p3.Y());
	glVertex2f(p4.X(), p4.Y());
	glEnd();
}

void Application::RenderText(const char* text, const Vector2& position)
{
	if (m_font)
	{
		sf::String text(text, *m_font, 16);
		text.SetPosition(position.X(), position.Y());
		m_window->Draw(text);
	}
}

void Application::Prepare3DViewport(int left, int top, int width, int height)
{
	if (!m_is3D)
	{
		Init3D();
	}

	glEnable(GL_COLOR_MATERIAL);

	glViewport(left, top, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	const double verticalFov = 45.;
	const double aspectRatio = static_cast<double>(width) / height;
	const double nearPlane = 0.1;
	const double farPlane = 50.;

	gluPerspective(verticalFov,	aspectRatio, nearPlane, farPlane);
}

void Application::Prepare2DViewport(int left, int top, int width, int height)
{
	if (m_is3D)
	{
		Init2D();
	}

	glViewport(left, top, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(left, left + width, top + height, top);
}

void Application::OnResize(unsigned int width, unsigned int height)
{
	if (m_is3D)
		Prepare3DViewport(0, 0, width, height);
	else
		Prepare2DViewport(0, 0, width, height);
}

void Application::RenderTextLine(const char* text)
{
	RenderText(text, m_nextTextLinePosition);
	m_nextTextLinePosition.Y() += m_fontSize;
}
